#include "Terrain.hpp"

#include <vector>
#include <string>
#include "Image.hpp" //


Terrain::Terrain()
  : m_vertexPerLines(512u),
    m_patchPerLines(32u),
    m_patchSize(0.0f),
    m_geometryScale(1.0f),
    m_bUseDynamicLOD(true)
{
}

Terrain::~Terrain()
{
  m_attribTex.release();
  
  for (int i=0; i<kNumTextureMaterial; ++i) {
    m_materialTex[i].release();
  }
}

void Terrain::init()
{
  initHeightmap();
  initGeometry();
  initTexture();
  initShader();
  
  m_grass.init( m_heightmap );
  
  CHECKGLERROR();
}


void Terrain::initHeightmap()
{
  const float xzScale = m_geometryScale*m_vertexPerLines;
  const float yScale = Grass::kTerrainMaxHeight;
  
  /// Generate the heightmap
  aer::Vector3 scales( xzScale, yScale, xzScale);  
  m_heightmap.generate( m_vertexPerLines, m_vertexPerLines, scales);
  
  
  /// Generate the attribute texture [RGB Normals + A Height texture]
  const aer::Heightmap::HMVertex *pVertices = m_heightmap.getVertices();
    
  float *terrainDatas = new float[4u*m_vertexPerLines*m_vertexPerLines];
  for (int i=0; i<m_vertexPerLines; ++i)
  {
    for (int j=0; j<m_vertexPerLines; ++j)
    {
      int index = i + j*m_vertexPerLines;
    
      terrainDatas[ 4*index + 0 ] = pVertices[index].normal.x;
      terrainDatas[ 4*index + 1 ] = pVertices[index].normal.y;
      terrainDatas[ 4*index + 2 ] = pVertices[index].normal.z;
      
      terrainDatas[ 4*index + 3 ] = pVertices[index].position.y;
    }
  }  

  m_attribTex.generate();
  m_attribTex.bind();
    m_attribTex.setFilters( aer::Texture::LINEAR, aer::Texture::LINEAR);
    m_attribTex.setWraps( aer::Texture::CLAMP_TO_EDGE, aer::Texture::CLAMP_TO_EDGE);

    m_attribTex.setImage( aer::Texture::TEXTURE_2D,
                          GL_RGBA32F, m_vertexPerLines, m_vertexPerLines, GL_RGBA, GL_FLOAT, 
                          terrainDatas);
    delete [] terrainDatas;    
  m_attribTex.unbind();

#if 0
  // [ Generate the heightmap on a shader ] (not used)
  
  aer::Program program;
  program.generate();
    program.addShader( aer::Program::VERTEX_SHADER,   "ScreenMapping.Vertex" );
    program.addShader( aer::Program::FRAGMENT_SHADER, "TexGen.Terrain.Fragment" );
    program.addShader( aer::Program::FRAGMENT_SHADER, "Noise.Include" );
  program.link();
  
  
  aer::RenderTexture heightmapRT;  
  
  heightmapRT.bind();
  heightmapRT.setup( &m_attribTex );  
  //heightmapRT.resize( w, h); //TODO
  
  heightmapRT.begin(); 
    program.bind();
      program.setUniform( "uEnableTiling", true);
      program.setUniform( "uTileRes", aer::Vector3(res));
      
      int seed = glm::linearRand(0.0f, 456789.0f);//
      program.setUniform( "uPermutationSeed", seed);
      
      ScreenQuad::Draw();
    program.unbind();  
  heightmapRT.end();
  
  program.release();
  heightmapRT.release();
#endif  
}

void Terrain::initGeometry()
{
  /// Creates the base PATCH mesh geometry for the tesselation
   
  std::vector<aer::Vector2> vertices( m_patchPerLines * m_patchPerLines );
  m_patchSize = m_geometryScale * (m_vertexPerLines / float(m_patchPerLines));
    
  for (int j=0, idx=0; j<m_patchPerLines; ++j)
  {
    float y = j * m_patchSize;
    for (int i=0; i<m_patchPerLines; ++i, ++idx)
    {
      vertices[idx].x = i * m_patchSize;
      vertices[idx].y = y;
    }
  }
  
  m_mesh.setAttrib( aer::Mesh::POSITION, (float*)(vertices.data()), 2, vertices.size());
  m_mesh.setPrimitiveType( GL_PATCHES );
  
  m_mesh.upload( aer::DeviceBuffer::STATIC_DRAW );
}


void Terrain::initTexture()
{   
  std::string texturePath( "../IslandGL4/data/terrain/" ); //
  
  const char *names[] = { "sand_diffuse.dds", "rock_diffuse.dds", "grass_diffuse.dds",
                           "slope_diffuse.dds", "normalmap0.dds", "normalmap1.dds",
                           "normalmap2.dds" };
  
  int maxAnisoLevel = 1u;
  if (GL_EXT_texture_filter_anisotropic) {
    glGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisoLevel);
  }
  printf("anisotropy level : %d\n", maxAnisoLevel);
  
  Image_t img;    
  for (int i=0; i<kNumTextureMaterial; ++i)
  {
    aer::Texture &tex = m_materialTex[i];
    
    img.load( (texturePath + names[i]).c_str() );
    AER_CHECK( img.target == GL_TEXTURE_2D );
    
    tex.generate();
    tex.bind();
    tex.setWraps( aer::Texture::REPEAT, aer::Texture::REPEAT);
    
    if (GL_EXT_texture_filter_anisotropic) {
      glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisoLevel);//
    }
      
    tex.setImage( aer::Texture::TEXTURE_2D,
                  img.internalFormat, img.width, img.height, img.format, img.type,
                  img.data );
      
    glGenerateMipmap( GL_TEXTURE_2D ); //
  }
  aer::Texture::Unbind( aer::Texture::TEXTURE_2D, 0u);
}

void Terrain::initShader()
{
  m_program.generate();
    m_program.addShader( aer::Program::VERTEX_SHADER,          "Terrain.Vertex");
    m_program.addShader( aer::Program::TESS_CONTROL_SHADER,    "Terrain.TessControl");
    m_program.addShader( aer::Program::TESS_EVALUATION_SHADER, "Terrain.TessEval");
    m_program.addShader( aer::Program::FRAGMENT_SHADER,        "Terrain.Fragment");
  m_program.link();
}


void Terrain::render(const aer::Camera &camera)
{  
  
  /// Terrain rendering with tesselation
  m_program.bind();
  {
    m_program.setUniform( "uModelViewProjMatrix", camera.getViewProjectionMatrix() );
    m_program.setUniform( "uEyePos", camera.getPosition() );
    m_program.setUniform( "uEyeDir", camera.getDirection() );
    
    m_program.setUniform( "uPatchSize", m_patchSize );
    m_program.setUniform( "uTerrainScaledResolution", m_geometryScale*m_vertexPerLines );
    m_program.setUniform( "uUseDynamicLOD", m_bUseDynamicLOD );  
    
    m_program.setUniform( "uTerrainTex", 0);    
    m_program.setUniform( "uSandDiffuseTex",  1+ TEX_SAND);
    m_program.setUniform( "uRockDiffuseTex",  1+ TEX_ROCK);
    m_program.setUniform( "uGrassDiffuseTex", 1+ TEX_GRASS);
    m_program.setUniform( "uSlopeDiffuseTex", 1+ TEX_SLOPE);
    m_program.setUniform( "uNormalmap0Tex",   1+ TEX_NORMALMAP0);
    m_program.setUniform( "uNormalmap1Tex",   1+ TEX_NORMALMAP1);
    m_program.setUniform( "uNormalmap2Tex",   1+ TEX_NORMALMAP2);
    
    // binds terrain attrib texture
    m_attribTex.bind( 0 );
    
    // binds terrain material textures
    for (int i=0; i<kNumTextureMaterial; ++i) {
      m_materialTex[i].bind( i+1 );
    }
    
    glPatchParameteri( GL_PATCH_VERTICES, 1u ); //
    m_mesh.draw();
    
    // unbind textures
    for (int i=0; i<kNumTextureMaterial; ++i) {
      m_materialTex[i].unbind();
    }
    m_attribTex.unbind();
  }
  m_program.unbind();
  
  
  /// Render the grass after the terrain [to use the Depth Buffer]
  m_grass.render( camera );
  
  
  CHECKGLERROR();
}
